﻿/* 
 *  <copyright file="TexturedQuadCmp.cs" company="Wesley Jowitt">
 *  
 *  SobrietyEngine: Copyright (c) 2011 Wesley Jowitt
 *  
 *  This software is distributed under a software license.
 *   * As of the creation time of this file, the license in use is the Microsoft Permissive License (Ms-PL) 
 *    * The license can be viewed at:
 *     * License.Txt in the root folder of this project
 *      * http://sobrietyengine.codeplex.com/license
 *       * http://www.microsoft.com/opensource/licenses.mspx
 *       
 *  </copyright>
 *   */

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SobrietyEngine.Actor;
using FarseerPhysics;


namespace SobrietyEngine.Components.Graphic
{
    public class TexturedPolygonCmp : Base3DGraphic, SobrietyEngine.Components.IComponent
    {
        protected BasicEffect basicEffect;
        protected Texture2D texture;
        protected String textureName;
        GraphicsDevice graphics;
        Vector3[] polyContour;
        Matrix staticWorld;

        Boolean reverseTriangles = false;

        VertexPositionNormalTexture[] triangleData;
        int[] vertInd;

        IndexBuffer indBuff;
        VertexBuffer vertBuff;

        Boolean hasHoles = false;
        static DepthStencilState stencilAlways;
        static BlendState blendStateNoColor;
        static DepthStencilState stencilKeepIfOne;

        List<Vector3[]> holeContours = new List<Vector3[]>();
        List<VertexPositionNormalTexture[]> holeTriangleData = new List<VertexPositionNormalTexture[]>();
        List<int[]> holeVertInd = new List<int[]>();
        List<IndexBuffer> holeIndexBuffer = new List<IndexBuffer>();
        List<VertexBuffer> holeVertexBuffer = new List<VertexBuffer>();

        Matrix world;
        public Boolean Static { get; set; }

        IOrientation Rotation;
        IPosition Position;
        IScale Scale;

        Vector3 scale = new Vector3(1, 1, 1);

        Vector3 rot = Vector3.Zero;
        Vector3 pos = new Vector3();


        public TexturedPolygonCmp()
            : base()
        {
            Static = false;
        }


        public TexturedPolygonCmp(String _textureName)
            : base()
        {
            textureName = _textureName;
        }

        /// <summary>
        /// Initializes a new args of the <see cref="TexturedQuadCmp"/> class.
        /// </summary>
        /// <param name="_textureName">Name of the _texture.</param>
        /// <param name="quadVectors">The quad vectors, in order of LL, UL, LR, UR</param>
        public TexturedPolygonCmp(String _textureName, Vector3[] polygonVectors)
        {
            textureName = _textureName;
            polyContour = polygonVectors;
        }

        public TexturedPolygonCmp(String _textureName, Vector3[] polygonVectors, Matrix scaleRotTrans)
        {
            textureName = _textureName;
            polyContour = polygonVectors;
            staticWorld = scaleRotTrans;
            Static = true;
        }

        public TexturedPolygonCmp(String _textureName, Vector3[] polygonVectors, Matrix scaleRotTrans, Boolean reverseTris)
        {
            textureName = _textureName;
            polyContour = polygonVectors;
            staticWorld = scaleRotTrans;
            Static = true;
            reverseTriangles = reverseTris;
        }


        public void AddHole(Vector3[] holeVerts, Matrix scaleRotTrans, Boolean reverseTris)
        {
            hasHoles = true;
            holeContours.Add(holeVerts);

            VertexPositionNormalTexture[] triData;
            int[] vertInd;
            IndexBuffer indexBuffer;
            VertexBuffer vertexBuffer;

            TriangulatePoly(reverseTris, out triData, out vertInd, holeVerts, out vertexBuffer, out indexBuffer);

            holeTriangleData.Add(triData);
            holeVertInd.Add(vertInd);
            holeIndexBuffer.Add(indexBuffer);
            holeVertexBuffer.Add(vertexBuffer);
        }

        override public Boolean InitComponent()
        {
            graphics = Actor.Game.GraphicsDevice;

            //stencil buffer states for drawing holes
            if (stencilAlways == null)
            {
                stencilAlways = new DepthStencilState();
                stencilAlways.StencilEnable = true;
                stencilAlways.StencilFunction = CompareFunction.Always;
                stencilAlways.StencilPass = StencilOperation.Replace;
                stencilAlways.ReferenceStencil = 1;
                stencilAlways.DepthBufferEnable = false;
            }

            if (stencilKeepIfOne == null)
            {
                stencilKeepIfOne = new DepthStencilState();
                stencilKeepIfOne.StencilEnable = true;
                stencilKeepIfOne.StencilFunction = CompareFunction.Equal;
                stencilKeepIfOne.StencilPass = StencilOperation.Keep;
                stencilKeepIfOne.ReferenceStencil = 0;
                stencilKeepIfOne.DepthBufferEnable = true;
            }

            if (blendStateNoColor == null)
            {
                blendStateNoColor = new BlendState();
                blendStateNoColor.ColorWriteChannels = ColorWriteChannels.None;
            }

            Rotation = Actor.Get<IOrientation>();

            Position = Actor.Get<IPosition>();
            if (Position == null)
                throw new Exception("Position required");

            Scale = Actor.Get<IScale>();
            if (Scale != null)
                scale = Scale.Scale;

            //if we are set to static, we want to make sure the position, scaleCmp, and Orientation componentshave been
            //initialized already. also check farseer body, since it could potentially control those things
            IFarseerPhysicsBody fpb = Actor.Get<IFarseerPhysicsBody>();
            if (fpb != null)
            {
                IComponent c = fpb as IComponent;
                if (c.Initialized == false)
                    return false; //delay
            }

            //get our texture
            if (texture == null)
            {
                //try to laod a texture
                texture = Actor.GameServices().TextureGameService[textureName];
                if (texture == null)
                    Logging.Logger.GetSingleton().WriteError("TexturedQuadCmp::Initialize()-> null texture");
            }

            

            
            //triangualte our poly
            TriangulatePoly(reverseTriangles, out triangleData, out vertInd, polyContour, out vertBuff, out indBuff);


            //set worldUp our basic effect
            basicEffect = new BasicEffect(graphics);
            basicEffect.EnableDefaultLighting();
            basicEffect.TextureEnabled = true;
            basicEffect.Texture = texture;

            //lets calculate our initial position, Orientation, scaleCmp.
            //and calculate our world matrix. if we are set to static, this is only done once
            //Orientation = roation something

            pos = Position.PositionV3;
            rot = Rotation != null ? Rotation.Forward : Vector3.Forward;
            Vector3 up = Rotation != null ? Rotation.Up : Vector3.Up;

            if (staticWorld != null)
                basicEffect.World = staticWorld;
            else
                basicEffect.World = Matrix.CreateScale(scale) * 
                    Matrix.CreateWorld(Vector3.Zero, rot, up) *
                    Matrix.CreateTranslation(pos);

            world = basicEffect.World;

            return true;
        }

        
        public void TriangulatePoly(Boolean reverseTriangles, out VertexPositionNormalTexture[] triData, 
            out int[] vertIndecies, Vector3[] contour, out VertexBuffer vBuff, out IndexBuffer iBuff)
        {
            //create polygons
            //todo: check for concave/convex
            //use earclipping to break apart concave
            //use stencilo trick or other magic to make holes work
            //make all polygons work throughthis component. will require some work.

            //convert 3D vectors into 2D for triangulation            

            triData = new VertexPositionNormalTexture[contour.Length];
            Vector3 norm = new Vector3(0, 0, 1);
            Vector2 texturePos = new Vector2(0, 1);

            FarseerPhysics.Common.Vertices farseerVerts = new FarseerPhysics.Common.Vertices();
            
            for (int i = contour.Length - 1; i > 0; --i)
            //for(int i=0; i < contour.Length; ++i)
            {
                //for triangulating
                farseerVerts.Add(new Vector2(contour[i].X, contour[i].Y));

                //create our vertex buffer info
                triData[i] = new VertexPositionNormalTexture(contour[i], norm, texturePos);
            }

            //decompose into triangles
            List<FarseerPhysics.Common.Vertices> decompTriangles =
                FarseerPhysics.Common.Decomposition.FlipcodeDecomposer.ConvexPartition(farseerVerts);

            //create out triangle index list
            vertIndecies = new int[decompTriangles.Count * 3];

            //now we need to make an index buffer for our triangle list
            //this is slow. do it better.
            //go through each vert in each triangle, look worldUp its index in the triData vert, add the index  
            int curTriInd = 0;

            if (reverseTriangles == false)
            {
                foreach (FarseerPhysics.Common.Vertices verts in decompTriangles)
                {
                    foreach (Vector2 vert in verts)
                    {
                        for (int i = 0; i < triData.Length; ++i)
                        {
                            if (triData[i].Position.X == vert.X &&
                                triData[i].Position.Y == vert.Y)
                            {
                                vertIndecies[curTriInd] = i;
                                ++curTriInd;
                                break;
                            }
                        }
                    }
                }
            }
            else
            {
                for (int vi = decompTriangles.Count - 1; vi >= 0; --vi)
                {
                    FarseerPhysics.Common.Vertices verts = decompTriangles[vi];
                    for (int ti = verts.Count - 1; ti >= 0; --ti)
                    {
                        Vector2 vert = verts[ti];
                        for (int i = 0; i < triData.Length; ++i)
                        {
                            if (triData[i].Position.X == vert.X &&
                                triData[i].Position.Y == vert.Y)
                            {
                                vertIndecies[curTriInd] = i;
                                ++curTriInd;
                                break;
                            }
                        }
                    }
                }
            }

            //create our vertex buffer
            vBuff = new VertexBuffer(graphics, typeof(VertexPositionNormalTexture), triData.Length, BufferUsage.WriteOnly);
            vBuff.SetData(triData);

            iBuff = new IndexBuffer(graphics, typeof(int), vertIndecies.Length, BufferUsage.WriteOnly);
            iBuff.SetData(vertIndecies);
        }

        public override void UpdateGraphic(GameTime time)
        {
            if (!Static)
            {
                if (Rotation != null)
                    rot = Rotation.Forward;
                Vector3 up = Rotation != null ? Rotation.Up : Vector3.Up;

                if (Scale != null)
                    scale = Scale.Scale;

                pos = Position.PositionV3;

                world = Matrix.CreateScale(scale) *
                        Matrix.CreateWorld(Vector3.Zero, rot, up) *
                        Matrix.CreateTranslation(pos);
            }
        }


        public void DrawPoly(IndexBuffer indexBuffer, VertexBuffer vertexBuffer, Texture2D tex)
        {
            if (basicEffect.Texture != tex)
                basicEffect.Texture = tex;

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                graphics.SetVertexBuffer(vertexBuffer);
                graphics.Indices = indexBuffer;
                int noVertices = vertexBuffer.VertexCount;
                int noTriangles = indexBuffer.IndexCount / 3;
                graphics.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, noVertices, 0, noTriangles);
            }            
        }

        public override void Draw(ICamera _camera, Matrix _projection, Matrix _view, Matrix _world)
        {
            basicEffect.Projection = _projection;
            basicEffect.View = _view;
            basicEffect.World = _world * world;

            if (hasHoles)
            {
                GraphicsDevice gd = Actor.GameServices().GraphicDM.GraphicsDevice;
                               

                //set to stencil mode form the holes
                gd.BlendState = blendStateNoColor;
                gd.DepthStencilState = stencilAlways;

                //draw holes
                for (int i = 0; i < holeVertexBuffer.Count; ++i)
                {
                    DrawPoly(holeIndexBuffer[i], holeVertexBuffer[i], texture);
                }

                //now regular poly
                //draw only at 0


                gd.BlendState = BlendState.AlphaBlend;
                gd.DepthStencilState = stencilKeepIfOne;

                DrawPoly(indBuff, vertBuff,texture);

                gd.Clear(ClearOptions.Stencil, new Vector4(0, 0, 0, 1), 0, 0);
                gd.DepthStencilState = DepthStencilState.Default;
            }

            else
                //draw our textured poly, no holes
                DrawPoly(indBuff, vertBuff, texture);

        }
    }
}